﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Networking;

namespace Nirvana
{
    /// <summary>
    /// AB任务加载类，用于从网络缓存中下载Ab，并加载
    /// </summary>
    public sealed class AssetBundleLoadTask
    {
        private Logger logger = LogSystem.GetLogger("AssetBundleLoadTask");
        private static int maxDownCount = 5;
        private static int curDownCount = 0;

        private TaskManager taskmgr;
        private string downloadUrl;
        private UnityWebRequest unityWebRequest;
        private bool isStart;
        private string assetPath;
        private AssetBundleCache abCache;
        private AssetBundleCreateRequest abRequest;//异步加载本地Ab
        private string abName;
        private Hash128 hash;
        private ulong downBytes;
        private float downProgress;
        private ulong downSpeed;
        private float flushTime;
        private float unscaledDeltaTime;
        private ulong curDownBytes;
        private int maxErrorCount = 6;
        private int curErrorCount;
        private AssetBundle assetBundle;
        private string error;
        private bool isLoading;

        public static void MaxDownCount(int count)
        {
            maxDownCount = count;
        }

        public static void ResetDownCount()
        {
            curDownCount = 0;
        }

        public bool IsLoading
        {
            get { return isLoading; }
            set { isLoading = value; }
        }
        public string Error
        {
            get { return error; }
            set { error = value; }
        }
        public AssetBundle AssetBundle
        {
            get { return assetBundle; }
            set { assetBundle = value; }
        }

        public string DownloadUrl
        {
            get { return downloadUrl; }
            set { downloadUrl = value; }
        }

        public ulong DownBytes
        {
            get { return downBytes; }
        }

        public float DownProgress
        {
            get { return downProgress; }
        }

        public ulong DownSpeed
        {
            get { return downSpeed; }
        }

        public string AssetBundleName
        {
            get { return this.abName; }
        }

        public Hash128 Hash
        {
            get { return this.hash; }
        }

        /// <summary>
        /// 包体大小，默认0
        /// </summary>
        public ulong Size
        {
            get { return 0; }
        }

        public  AssetBundleLoadTask(bool isloading,TaskManager task,string downUrl,AssetBundleCache abCache,string abName,Hash128 hash )
        {
            this.isLoading = isloading;
            this.taskmgr = task;
            this.downloadUrl = downUrl;
            this.abCache = abCache;
            this.abName = abName;
            this.hash = hash;
            ResetFpsTime(0.05f);
        }
        public AssetBundleLoadTask(bool isloading, TaskManager task, string downUrl, AssetBundleCache abCache, string abName)
        {
            this.isLoading = isloading;
            this.taskmgr = task;
            this.downloadUrl = downUrl;
            this.abCache = abCache;
            this.abName = abName;
            ResetFpsTime(0.05f);
        }

        public AssetBundleLoadTask(AssetBundleCache abCache,AssetBundleCreateRequest abRequest, string abName,Hash128 hash)
        {
            this.isLoading = false;
            this.abCache = abCache;
            this.abRequest = abRequest;
            this.abName = abName;
            this.hash = hash;
        }

        public AssetBundleLoadTask(AssetBundleCache abCache, AssetBundleCreateRequest abRequest, string abName)
        {
            this.isLoading = false;
            this.abCache = abCache;
            this.abRequest = abRequest;
            this.abName = abName;
        }

        public void StartLoadTask()
        {
            Assert.IsNull<UnityWebRequest>(this.unityWebRequest);
            if (this.hash.isValid)
            {
                this.assetPath = this.abCache.GetCachePath(this.abName, this.hash);
            }
            else
            {
                this.assetPath = this.abCache.GetCachePath(this.abName);
            }

            this.unityWebRequest = UnityWebRequest.Get(this.downloadUrl);
            this.unityWebRequest.SendWebRequest();
            this.isStart = true;
        }

        public bool ExcuteLoadTask()
        {
            if (unityWebRequest != null)
                this.LoadTask();
            else
                this.DeleteTask();

            return string.IsNullOrEmpty(this.error)
                && (!this.isLoading || !this.isStart || this.IsHaveRequest())
                && this.assetBundle == null;
        }

        public bool IsHaveRequest()
        {
            return this.unityWebRequest != null;
        }

        private void ResetFpsTime(float t)
        {
            this.flushTime = t;
            this.unscaledDeltaTime = 0f;
            this.curDownBytes = 0;
        }

        private void UpdateDownSpeed(ulong t)
        {
            this.flushTime -= Time.unscaledDeltaTime;
            this.unscaledDeltaTime += Time.unscaledDeltaTime;
            this.curDownBytes += t;
            if(this.flushTime <= 0f)
            {
                this.downSpeed = (ulong)(this.curDownBytes / this.unscaledDeltaTime);
                if (this.downSpeed == 0) this.ResetFpsTime(0.05f);
                else this.ResetFpsTime(0.5f);
            }
        }


        private void LoadTask()
        {
            ulong num = this.unityWebRequest.downloadedBytes - this.downBytes;
            this.downBytes = this.unityWebRequest.downloadedBytes;
            this.downProgress = this.unityWebRequest.downloadProgress;
            this.UpdateDownSpeed(num);
            if (this.unityWebRequest.isNetworkError)
            {
                this.error = string.Format("http load error: {0} {1}", this.downloadUrl, this.unityWebRequest.error);
                this.unityWebRequest.Dispose();
                this.unityWebRequest = null;
                this.taskmgr.ReFreshTask(this, ++this.curErrorCount <= this.maxErrorCount);
            }
            else if(this.unityWebRequest.isHttpError || this.unityWebRequest.responseCode < 0L || this.unityWebRequest.responseCode >= 400L)
            {
                this.error = string.Format("http load error: {0} {1}", this.downloadUrl, this.unityWebRequest.responseCode);
                this.unityWebRequest.Dispose();
                this.unityWebRequest = null;
                this.taskmgr.ReFreshTask(this, ++this.curErrorCount <= this.maxErrorCount);
            }else if (this.unityWebRequest.isDone)
            {
                if(curDownCount < maxDownCount)
                {
                    curDownCount++;
                    try
                    {
                        string dirName = Path.GetDirectoryName(this.assetPath);
                        if (!Directory.Exists(dirName))
                        {
                            Directory.CreateDirectory(dirName);
                        }
                        File.WriteAllBytes(this.assetPath, this.unityWebRequest.downloadHandler.data);
                    }
                    catch (Exception ex)
                    {
                        this.error = ex.Message;
                        this.unityWebRequest.Dispose();
                        this.unityWebRequest = null;
                        this.taskmgr.ReFreshTask(this, ++this.curErrorCount <= this.maxErrorCount);
                        return;
                    }
                    this.unityWebRequest.Dispose();
                    this.unityWebRequest = null;
                    this.ReadAssetBundle();
                }
                this.taskmgr.ReFreshTask(this, false);
            }
        }

        private void ReadAssetBundle()
        {
            if (this.isLoading) return;
            if(this.abCache != null)
            {
                if (this.hash.isValid)
                {
                    Assert.IsNotNull<AssetBundleCache>(this.abCache);
                    this.abRequest = this.abCache.LoadFromFileAsync(this.abName, this.hash, false);
                    if(this.abRequest == null)
                        this.error = string.Format("The asset at: {0} with hash {1} is not in the cache.", this.unityWebRequest.url, this.hash);
                }
                else
                {
                    Assert.IsNotNull<AssetBundleCache>(this.abCache);
                    this.abRequest = this.abCache.LoadFromFileAsync(this.abName);
                    if (this.abRequest == null)
                        this.error = string.Format("The asset at: {0}  is not in the cache.", this.unityWebRequest.url);

                }
            }
            else
            {
                /*
                 * UnityWebRequest API允许开发人员精确地指定Unity 应如何处理下载的数据，
                 * 并允许开发人员消除不必要的内存使用。使用UnityWebRequest下载AssetBundle 
                 * 的最简单方法是调用UnityWebRequest.GetAssetBundle。
                 * DownloadHandlerAssetBundle。它使用工作线程，将下载的数据流存储到一个固定大小的缓冲区中，
                 * 然后根据下载处理程序的配置方式将缓冲数据放到临时存储或AssetBundle缓存中。
                 */
                AssetBundle ab = DownloadHandlerAssetBundle.GetContent(this.unityWebRequest);
                if(ab != null)
                {
                    UpdateAssetBundle(ab);
                }
                else
                {
                    this.error = string.Format("The asset at: {0}  is not an asset bundle.", this.unityWebRequest.url);
                }
            }
        }

        private void DeleteTask()
        {
            if (this.abRequest == null || !this.abRequest.isDone) return;

            if(this.abRequest.assetBundle != null)
            {
                if (LogSystem.IsLogAct(LogActType.LoadAssetBundleComplete))
                {
                    logger.LogInfo(string.Format("LoadAssetBundleComplete {0}", this.assetBundle));
                }
                this.UpdateAssetBundle(this.abRequest.assetBundle);
                return;
            }

            if(this.abCache != null)
            {
                if (this.hash.isValid)
                {
                    this.abCache.Delete(this.abName, this.hash);
                    this.error = string.Format("The asset at: {0} with hash {1} in cache is not an asset bundle.", this.abName, this.hash);
                }
                else
                {
                    this.abCache.Delete(this.abName);
                    this.error = string.Format("The asset at: {0} in cache is not an asset bundle.", this.abName);
                }
            }
            else
            {
                this.error = string.Format("The asset at: {0} with hash {1}  is not an asset bundle.", this.abName, this.hash);
            }

        }

        private void UpdateAssetBundle(AssetBundle ab)
        {
            this.assetBundle = ab;
            if (!ab.isStreamedSceneAssetBundle)//是否场景资源
            {
                Material[] mats = ab.LoadAllAssets<Material>();
                foreach(var m in mats)
                {
                    Shader shader = Shader.Find(m.shader.name);
                    if (shader != null) m.shader = shader;
                }
            }
        }


    }
}

